﻿using JESAI.DistributedLock.Internal;
using JESAI.DistributedLock.Redis.Primitives;
using JESAI.DistributedLock.Redis.RedLock;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.DistributedLock.Redis;

/// <summary>
/// 使用Redis实现<see cref="IDistributedSemaphore"/>接口。
/// 该类用于管理分布式信号量，提供了尝试获取信号量的方法。
/// </summary>

public sealed partial class RedisDistributedSemaphore : IInternalDistributedSemaphore<RedisDistributedSemaphoreHandle>
{
    /// <summary>
    /// 注意：虽然我们将其存储为列表以简化与RedLock组件的交互，但实际上信号量算法仅适用于单个数据库。如果使用多个数据库，我们可能会违反<see cref="MaxCount"/>。
    /// 例如，对于3个数据库和2个令牌，我们可能会有3个用户获取AB、BC和AC。每个数据库都看到2个令牌被占用！
    /// </summary>

    private readonly IReadOnlyList<IDatabase> _databases;
    private readonly RedisDistributedLockOptions _options;

    /// <summary>
    /// 使用提供的<paramref name="maxCount"/>、<paramref name="database"/>和<paramref name="options"/>构造一个名为<paramref name="key"/>的信号量。
    /// </summary>
    public RedisDistributedSemaphore(RedisKey key, int maxCount, IDatabase database, Action<RedisDistributedSynchronizationOptionsBuilder>? options = null)
    {
        if (key == default(RedisKey)) { throw new ArgumentNullException(nameof(key)); }
        if (maxCount < 1) { throw new ArgumentOutOfRangeException(nameof(maxCount), maxCount, "must be positive"); }
        this._databases = new[] { database ?? throw new ArgumentNullException(nameof(database)) };

        this.Key = key;
        this.MaxCount = maxCount;
        this._options = RedisDistributedSynchronizationOptionsBuilder.GetOptions(options);
    }

    internal RedisKey Key { get; }

    /// <summary>
    /// 实现 <see cref="IDistributedSemaphore.Name"/>
    /// </summary>
    public string Name => this.Key.ToString();

    /// <summary>
    /// 实现 <see cref="IDistributedSemaphore.MaxCount"/>
    /// </summary>
    public int MaxCount { get; }

    ValueTask<RedisDistributedSemaphoreHandle?> IInternalDistributedSemaphore<RedisDistributedSemaphoreHandle>.InternalTryAcquireAsync(TimeoutValue timeout, CancellationToken cancellationToken) =>
        BusyWaitHelper.WaitAsync(
            state: this,
            tryGetValue: (@this, cancellationToken) => @this.TryAcquireAsync(cancellationToken),
            timeout: timeout,
            minSleepTime: this._options.MinBusyWaitSleepTime,
            maxSleepTime: this._options.MaxBusyWaitSleepTime,
            cancellationToken: cancellationToken
        );

    private async ValueTask<RedisDistributedSemaphoreHandle?> TryAcquireAsync(CancellationToken cancellationToken)
    {
        var primitive = new RedisSemaphorePrimitive(this.Key, this.MaxCount, this._options.RedLockTimeouts);
        var tryAcquireTasks = await new RedLockAcquire(primitive, this._databases, cancellationToken).TryAcquireAsync().ConfigureAwait(false);
        return tryAcquireTasks != null
            ? new RedisDistributedSemaphoreHandle(new RedLockHandle(primitive, tryAcquireTasks, extensionCadence: this._options.ExtensionCadence, expiry: this._options.RedLockTimeouts.Expiry))
            : null;
    }
}
